"""
Copyright 2012 Wieser Dietmar, Margit Mutschlechner.

This file is part of Multimedia Social Networking Platform.

Multimedia Social Networking Platform is free software: 
you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Multimedia Social Networking Platform is distributed
in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Multimedia Social Networking Platform.  
If not, see <http://www.gnu.org/licenses/>.

"""

from threading import Thread
from msnp.service.network import CustodyQueue, NetworkLock, BackupController
from msnp.service.network.sending import Sender
from msnp.service import network, utils
from msnp.logger import Logger
from msnp.service.network import logic
from msnp.service.network.model import Model
from msnp.persistence import PersistenceController
from msnp.service.utils import Hash
from msnp.service import synch
from msnp.model import PTPModel
from msnp.gui import MySignal
from msnp.service.network.logic.MessageExchangeLogic import executeCallbackPersonalMessage


def amIResponsibleIdentityFor(searchedHashedNameLong):
    """This function finds out if the current peer is responsible for the given hashed user name.
    
    Args:
        searchedHashedNameLong: The hashed user name (long).
        
    Returns:
        Returns True if the current peer is responsible, False otherwise.
    """

    myself = network.NetworkController.getMySelf()
    myIdentity = myself.getMyIdentity()
    successor = myself.getSuccessor()
    predecessor = myself.getPredecessor()
    if predecessor != None and successor != None:
        return isResponsible(predecessor.getHashedNameLong(), searchedHashedNameLong, myIdentity.getHashedNameLong())
    else:
        return True
    
    
def isResponsible(predecessorHashedNameLong, searchedHashedNameLong, responsibleHashedNameLong):
    """This function determines whether the searched hashed name is between the predecessor hashed name and the responsible hashed name.
    
    Args:
        predecessorHashedNameLong: A hashed user name (long).
        searchedHashedNameLong: A hashed user name (long).
        responsibleHashedNameLong: A hashed user name (long).
        
    Returns:
        Returns True if searchedHashedNameLong lies between responsibleHashedNameLong and predecessorHashedNameLong, False otherwise.
    """
    
    #if 'searchedHashedNameLong' is bigger than the predecessor but smaller than the other peer
    if predecessorHashedNameLong < searchedHashedNameLong:
        if responsibleHashedNameLong >= searchedHashedNameLong:
            return True    
    #if 'searchedHashedNameLong' is either bigger than the predecessor or smaller than the other one
    if predecessorHashedNameLong > responsibleHashedNameLong:
        if responsibleHashedNameLong >= searchedHashedNameLong or predecessorHashedNameLong < searchedHashedNameLong:
            return True
    #doesnt matter which username, I am responsible from me to me
    if predecessorHashedNameLong == responsibleHashedNameLong:
        return True
    return False


def responsibleIdentityReceived(receiverRespIdentity, receiverName, result, reactType):
    """This function is executed after the reception of a responsible identity.
    
    Until now there exists only one react type: SEARCHMEMBER_SENDMEDIA
    
    Args:
        receiverRespIdentity: the responsible identity of the receiver (PeerInfo instance)
        receiverName: the name of the receiver (string)
        result: the result type (defined by ReturnType)
        reactType: the function acts based on the reactType (defined by CallbackReactType)
    """

    if reactType == Model.CallbackReactType.SEARCHMEMBER_SENDMEDIA:
        if receiverRespIdentity == None:
            Logger.info('peer ' + receiverName + ' I searched to push media content does not exist???')
        else:
            if result == PTPModel.ReturnType.PEER_OFFLINE:
                Logger.info('peer ' + receiverName + ' I searched to push media content is offline...')
            else:
                Logger.info("send media content to " + receiverRespIdentity.getName() + " for peer " + receiverName)
                synch.NetSynchController.pushMediaContentToMember(receiverName, receiverRespIdentity)


##########################
## execute remote calls ##
##########################


def findResponsibleIdentityForAuth(searchedName, superPeerIdentity, isLogin):
    """This function calls the authentication-callback-function of the super peer identity if the current peer is responsible for the searched peer.
    Otherwise the call is forwarded to the nearest identity.
    
    Args:
        searchedName: the searched name (string)
        superPeerIdentity: the super peer identity (PeerInfo instance)
        isLogin: a boolean determining whether the executed authentication procedure is a registration or a login.
    """
    
    myself = network.NetworkController.getMySelf()
    searchedHashedNameLong = long(utils.Hash.hash(searchedName), 16)
    if amIResponsibleIdentityFor(searchedHashedNameLong):
        #if I am the searched identity, then I return None because the name is no more available
        #also check if its one of my low level peers
        itsMe = searchedName == myself.getMyIdentity().getName()
        itsMyLLP = myself.getLowLevelPeerByName(searchedName) != None
        if itsMe or itsMyLLP:
            identity = None
        #if I am responsible I return myself as the responsible peer
        else:
            identity = myself.getMyIdentity()
        if superPeerIdentity.getName() == myself.getMyIdentity().getName():
            executeCallbackResponsibleIdentityForAuth(identity, searchedName, isLogin)
        else:
            Sender.callbackResponsibleIdentityForAuth(identity, superPeerIdentity, searchedName, isLogin)
    #if I am not responsible I forward the search
    else:
        Sender.forwardResponsibilityCallForAuth(searchedName, superPeerIdentity, isLogin)


def findResponsibleIdentity(receiverName, senderName, senderRespIdentity, reactType):
    """This function calls the message-callback-function of the sender responsible identity if the current peer is responsible for the receiver.
    Otherwise the call is forwarded to the nearest identity.
    
    The identity is set to None if the searched user is not online.
    
    Args:
        receiverName: the receiver name (string)
        senderName: the sender name (string)
        senderRespIdentity: the responsible identity of the sender (PeerInfo instance)
        reactType: the react type is forwarded to other functions
    """
    
    myself = network.NetworkController.getMySelf()
    receiverHashedNameLong = long(utils.Hash.hash(receiverName), 16)
    if amIResponsibleIdentityFor(receiverHashedNameLong):
        amIReceiver = myself.getMyIdentity().getHashedNameLong() == receiverHashedNameLong
        isMyLowLevelReceiver = myself.getLowLevelPeerByName(receiverName) != None
        myIdentity = myself.getMyIdentity()
        #if I am responsible and the searched identity is me or one of my (online or offline) LLPs I return myself
        if PersistenceController.selectUserImResponsibleForUsername(receiverName) != None:
            identity = myIdentity
            if amIReceiver or isMyLowLevelReceiver:
                result = PTPModel.ReturnType.SUCCESSFUL
            else:
                result = PTPModel.ReturnType.PEER_OFFLINE
        #if I am responsible but the searched peer does not exist I return None
        else:
            identity = None
            result = PTPModel.ReturnType.ERROR
        if myIdentity.getName() == senderRespIdentity.getName():
            executeCallbackResponsibleIdentity(senderRespIdentity, senderName, receiverName, result, reactType)
        else:
            Sender.callbackResponsibleIdentity(identity, senderRespIdentity, senderName, receiverName, result, reactType)
    #if I am not responsible I forward the search
    else:
        Sender.forwardResponsibilityCall(receiverName, senderName, senderRespIdentity, reactType)


def forwardPersonalMessage(receiverName, senderName, senderRespIdentity, message, timestamp):
    """This function is called after the reception of a personal message.
    
    If this peer is not responsible it forwards the personal message.
    If this peer is the receiver it emits a chat message received signal.
    Otherwise if its one of its online low level peers it leaves a message in the custody queue,
    otherwise it does nothing (peer is offline).
    At the end it sends a callback with the corresponding status (SEND, PEER_OFFLINE, PEER_NOT_EXISTENT).
    
    Args:
        receiverName: the receiver name (string)
        senderName: the sender name (string)
        senderRespIdentity: the responsible identity of the sender (PeerInfo instance)
        message: the personal message (string)
        timestamp: the timestamp (float)
    """

    myself = network.NetworkController.getMySelf()
    receiverHashedNameLong = long(utils.Hash.hash(receiverName), 16)
    if myself.isLowLevelPeer() == False and amIResponsibleIdentityFor(receiverHashedNameLong):
        amIReceiver = myself.getMyIdentity().getHashedNameLong() == receiverHashedNameLong
        status = PTPModel.ChatMessageStatus.SENT
        if PersistenceController.selectUserImResponsibleForUsername(receiverName) != None:
            isMyLowLevelReceiver = myself.getLowLevelPeerByName(receiverName) != None
            if amIReceiver or isMyLowLevelReceiver:
                Logger.info("callback personal message status sent")
                if amIReceiver:
                    chatMessage = PTPModel.ChatMessage(senderName, message, timestamp, PTPModel.ChatMessageStatus.RECEIVED)
                    MySignal.emitChatMessageReceivedSignal(chatMessage)
                else:
                    CustodyQueue.addPersonalMessage(receiverName, senderName, message, timestamp)
                status = PTPModel.ChatMessageStatus.SENT
            else:
                Logger.info("callback personal message status peer offline")
                status = PTPModel.ChatMessageStatus.PEER_OFFLINE
        else:
            Logger.info("callback personal message status peer not existent")
            status = PTPModel.ChatMessageStatus.PEER_NOT_EXISTENT
        if myself.getMyIdentity().getName() == senderRespIdentity.getName():
            executeCallbackPersonalMessage(senderRespIdentity, senderName, receiverName, timestamp, status)
        else:
            Sender.callbackPersonalMessage(senderRespIdentity, senderName, receiverName, timestamp, status)
    #if I am not responsible I forward the search
    else:
        Sender.forwardPersonalMessage(receiverName, senderName, senderRespIdentity, message, timestamp)


def callbackResponsibleIdentityForAuth(receiverName, responsibleIdentity, isLogin):
    """This function adds a hello message to the custody queue, because the receiver is for sure a low level peer.
    
    Args:
        receiverName: the receiver name (string)
        responsibleIdentity: the responsible identity (PeerInfo instance)
        isLogin: a boolean determining whether the executed authentication procedure is a registration or a login.
    """
    
    CustodyQueue.addHelloMessage(receiverName, responsibleIdentity, isLogin)


def callbackResponsibleIdentity(receiverRespIdentity, senderName, receiverName, result, reactType):
    """Callback function for the search of a certain peer.
    
    It forwards the call if this peer is no more responsible for the receiver.
    If this peer is the receiver it calls the responsibleIdentityReceived function
    or it leaves a message in the custody queue if its a online low level peer.
    If the peer is offline the call is ignored.
    
    Args:
        receiverRespIdentity: the responsible identity of the receiver (PeerInfo instance)
        senderName: the sender name (string)
        receiverName: the receiver name (string)
        result: the result is forwarded to other functions
        reactType: the react type is forwarded to other functions
    """
    
    myName = network.NetworkController.getMySelf().getMyIdentity().getName()
    if senderName == myName:
        responsibleIdentityReceived(receiverRespIdentity, receiverName, result, reactType)
    else:
        lowLevelPeer = network.NetworkController.getMySelf().getLowLevelPeerByName(senderName)
        if lowLevelPeer != None:
            Logger.info("Responsible identity received for my low level " + senderName)
            CustodyQueue.addResponsibleIdentityFoundMessage(senderName, receiverRespIdentity, receiverName, result, reactType)
        else:
            hashedNameLong = long(Hash.hash(senderName), 16)
            if logic.ResponsibilityLogic.amIResponsibleIdentityFor(hashedNameLong):
                Logger.info("Groups received for a peer that is now offline: " + senderName)
            else:
                Logger.info("Groups received for a peer that is no more my low level peer: " + senderName)
                nearestIdentity = network.NetworkController.getMySelf().getNearestIdentity(hashedNameLong)[0]
                Sender.callbackResponsibleIdentity(receiverRespIdentity, nearestIdentity, senderName, receiverName, result, reactType)
    
    
def callbackResponsibleIdentityForFinger(identity, lowerFingerIndex, upperFingerIndex):
    """Callback function for finger candidates.
    
    It checks whether the peer is the best candidate for the given fingers and sets them.
    If the peer is not contained in the connected identities a backup gets send to it.
    If the finger was already set to a peer the backup is deleted from that peer.
    
    Args:
        identity: the received finger candidate (PeerInfo instance)
        lowerFingerIndex: the lower finger index (inclusive - integer)
        upperFingerIndex: the upper finger index (inclusive - integer)
    """
    
    NetworkLock.acquireFingerLock()
    NetworkLock.acquireBackupLock()
    try:
        Logger.info("got finger callback from " + identity.getName() + " -> range [" + str(lowerFingerIndex) + ", " + str(upperFingerIndex) + "]")
        mySelf = network.NetworkController.getMySelf()
        #check the range given from the callback (lowerFingerIndex and upperFingerIndex are inclusive)
        for i in range(lowerFingerIndex, upperFingerIndex + 1):
            shouldBeFinger = logic.FingerLogic.calculateFinger(mySelf.getMyIdentity().getHashedNameLong(), i, False)
            currentFinger = mySelf.getFingerById(i)
            #check if no other (better) finger was set in the meantime
            if currentFinger == None or isResponsible(shouldBeFinger, identity.getHashedNameLong(), currentFinger.getHashedNameLong()):
                allConnectedIdentities = network.NetworkController.getMySelf().getAllConnectedIdentities()
                if network.NetworkController.getMySelf().containsIdentity(allConnectedIdentities, identity) == False:
                    #shift backup from old finger to new finger only i
                    BackupController.insertAtFinger(identity)
                #set new finger
                Logger.info("  -> set finger " + str(i) + " to " + identity.getName())
                mySelf.setFinger(identity, i)
                #do it after setting finger -> otherwise currentFinger is always in connectedIdentities and backup doesn't get removed
                BackupController.deleteFromFinger(currentFinger)
    finally:
        NetworkLock.releaseFingerLock()
        NetworkLock.releaseBackupLock()
            

def findPredecessorOfCrashedIdentity(crashedIdentity, alreadyReceivedUsers):
    """This call is forwarded to the handleCrash function of the CrashLogic module.
    
    Args:
        crashedIdentity: the crashed identity (PeerInfo instance).
        alreadyReceivedUsers: a list containing peers that already received the message.
    """
    
    logic.CrashLogic.handleCrash(crashedIdentity, alreadyReceivedUsers)
    
    
##############################
## threads for remote calls ##
##############################

class FindResponsibleIdentityForAuthThread(Thread):
    """Thread to find the responsible identity for authentication.

    Attributes:
        __searchedName: The searched name (string)
        __superPeerIdentity: the super peer identity (PeerInfo instance)
        __isLogin: a boolean determining whether the executed authentication procedure is a registration or a login.
    """

    def __init__(self, searchedName, superPeerIdentity, isLogin):
        """the init method"""
        Thread.__init__(self)
        self.__searchedName = searchedName
        self.__superPeerIdentity = superPeerIdentity
        self.__isLogin = isLogin
        
    def run(self):
        """This method forwards the call to the findResponsibleIdentityForAuth function."""
        findResponsibleIdentityForAuth(self.__searchedName, self.__superPeerIdentity, self.__isLogin)


class CallbackResponsibleIdentityForAuthThread(Thread):
    """Thread that is executed after the reception of the responsible identity for authentication.
    
    Attributes:
        __receiverName: the receiver name (string)
        __responsibleIdentity: the responsible identity (PeerInfo instance)
        __isLogin: a boolean determining whether the executed authentication procedure is a registration or a login.
    """
    
    def __init__(self, responsibleIdentity, receiverName, isLogin):
        """the init method"""
        Thread.__init__(self)
        self.__responsibleIdentity = responsibleIdentity
        self.__receiverName = receiverName
        self.__isLogin = isLogin
    
    def run(self):
        """This method forwards the call to the callbackResponsibleIdentityForAuth function."""
        callbackResponsibleIdentityForAuth(self.__receiverName, self.__responsibleIdentity, self.__isLogin)
        
        
class FindResponsibleIdentityThread(Thread):
    """Thread that executes the search of a responsible identity.
    
    Attributes:
        __receiverName: the receiver name (string)
        __senderName: the sender name (string)
        __senderRespIdentity: the responsible identity of the sender (PeerInfo instance)
        __reactType: the react type is forwarded to other functions
    """
    
    def __init__(self, receiverName, senderName, senderRespIdentity, reactType):
        """the init method"""
        Thread.__init__(self)
        self.__receiverName = receiverName
        self.__senderName = senderName
        self.__senderRespIdentity = senderRespIdentity
        self.__reactType = reactType
        
    def run(self):
        """This method forwards the call to the findResponsibleIdentity function."""
        findResponsibleIdentity(self.__receiverName, self.__senderName, self.__senderRespIdentity, self.__reactType)
        
        
class CallbackResponsibleIdentityThread(Thread):
    """Thread that is executed after the reception of a searched identity.
    
    Attributes:
        __receiverRespIdentity: the responsible identity of the receiver (PeerInfo instance)
        __senderName: the sender name (string)
        __receiverName: the receiver name (string)
        __result: the result is forwarded to other functions
        __reactType: the react type is forwarded to other functions
    """
    
    def __init__(self, receiverRespIdentity, senderName, receiverName, result, reactType):
        """the init method"""
        Thread.__init__(self)
        self.__receiverRespIdentity = receiverRespIdentity
        self.__senderName = senderName
        self.__receiverName = receiverName
        self.__result = result
        self.__reactType = reactType

    def run(self):
        """This method forwards the call to the callbackResponsibleIdentity function."""
        callbackResponsibleIdentity(self.__receiverRespIdentity, self.__senderName, self.__receiverName, self.__result, self.__reactType)
            
            
class CallbackResponsibleIdentityForFingerThread(Thread):
    """Thread that is executed after the reception of finger candidates.
    
    Attributes:
        __identity: the received finger candidate (PeerInfo instance)
        __lowerFingerIndex: the lower finger index (inclusive - integer)
        __upperFingerIndex: the upper finger index (inclusive - integer)
    """
    
    def __init__(self, identity, lowerFingerIndex, upperFingerIndex):
        """the init method"""
        Thread.__init__(self)
        self.__identity = identity
        self.__lowerFingerIndex = lowerFingerIndex
        self.__upperFingerIndex = upperFingerIndex
        
    def run(self):
        """This method forwards the call to the callbackResponsibleIdentityForFinger function."""
        callbackResponsibleIdentityForFinger(self.__identity, self.__lowerFingerIndex, self.__upperFingerIndex)


class FindPredecessorOfCrashedIdentityThread(Thread):
    """Thread that performs the search of the predecessor of the crashed identity.
    
    Attributes:
        __crashedIdentity: the crashed identity (PeerInfo instance).
        __alreadyReceivedUsers: a list containing peers that already received the message.
    """
    
    def __init__(self, crashedIdentity, alreadyReceivedUsers):
        """the init method"""
        Thread.__init__(self)
        self.__crashedIdentity = crashedIdentity
        self.__alreadyReceivedUsers = alreadyReceivedUsers
        
    def run(self):
        """This method forwards the call to the findPredecessorOfCrashedIdentity function."""
        findPredecessorOfCrashedIdentity(self.__crashedIdentity, self.__alreadyReceivedUsers)


#####################
## execute threads ##
#####################

def executeFindResponsibleIdentityForAuth(searchedName, superPeerIdentity, isLogin):
    """This function executes a thread to find the responsible identity for authentication.

    Args:
        searchedName: The searched name (string)
        superPeerIdentity: the super peer identity (PeerInfo instance)
        isLogin: a boolean determining whether the executed authentication procedure is a registration or a login.
    """
    
    findThread = FindResponsibleIdentityForAuthThread(searchedName, superPeerIdentity, isLogin)
    findThread.start()
    
    
def executeCallbackResponsibleIdentityForAuth(responsibleIdentity, receiverName, isLogin):
    """This function executes a thread that is performed after the reception of the responsible identity for authentication.
    
    Args:
        receiverName: the receiver name (string)
        responsibleIdentity: the responsible identity (PeerInfo instance)
        isLogin: a boolean determining whether the executed authentication procedure is a registration or a login.
    """
    
    callbackThread = CallbackResponsibleIdentityForAuthThread(responsibleIdentity, receiverName, isLogin)
    callbackThread.start()
    
    
def executeFindResponsibleIdentity(receiverName, senderName, senderRespIdentity, reactType):
    """This function executes a thread that performs the search of a responsible identity.
    
    Args:
        receiverName: the receiver name (string)
        senderName: the sender name (string)
        senderRespIdentity: the responsible identity of the sender (PeerInfo instance)
        reactType: the react type is forwarded to other functions
    """
    
    findThread = FindResponsibleIdentityThread(receiverName, senderName, senderRespIdentity, reactType)
    findThread.start()
    
    
def executeCallbackResponsibleIdentity(receiverRespIdentity, senderName, receiverName, result, reactType):
    """This function executes a thread that is performed after the reception of a searched identity.
    
    Args:
        receiverRespIdentity: the responsible identity of the receiver (PeerInfo instance)
        senderName: the sender name (string)
        receiverName: the receiver name (string)
        result: the result is forwarded to other functions
        reactType: the react type is forwarded to other functions
    """
    
    callbackThread = CallbackResponsibleIdentityThread(receiverRespIdentity, senderName, receiverName, result, reactType)
    callbackThread.start()
    
    
def executeCallbackResponsibleIdentityForFinger(identity, lowerFingerIndex, upperFingerIndex):
    """This function executes a thread that is performed after the reception of finger candidates.
    
    Args:
        identity: the received finger candidate (PeerInfo instance)
        lowerFingerIndex: the lower finger index (inclusive - integer)
        upperFingerIndex: the upper finger index (inclusive - integer)
    """
    callbackThread = CallbackResponsibleIdentityForFingerThread(identity, lowerFingerIndex, upperFingerIndex)
    callbackThread.start()
    
    
def executeFindPredecessorOfCrashedIdentity(crashedIdentity, alreadyReceivedUsers):
    """This function executes a thread that performs the search of the predecessor of the crashed identity.
    
    Args:
        crashedIdentity: the crashed identity (PeerInfo instance).
        alreadyReceivedUsers: a list containing peers that already received the message.
    """
    
    forwardThread = FindPredecessorOfCrashedIdentityThread(crashedIdentity, alreadyReceivedUsers)
    forwardThread.start()
